<?php

namespace app\service\mqtt;

use Exception;
use support\Log;
use Workerman\Mqtt\Client;
use Workerman\Timer;

class MqttClient
{
    protected ?Client $mqttClient = null;
    protected string $connectionStatus = 'disconnected';
    protected int $reconnectAttempts = 0;
    protected ?int $reconnectTimerId = null;
    protected const MAX_RECONNECT_ATTEMPTS = 10; // 最大重连次数
    protected const RECONNECT_DELAY = 5; // 重连延迟(秒)
    protected const RECONNECT_BACKOFF_MULTIPLIER = 1.5; // 重连退避乘数
    // 默认订阅主题
    protected const DEFAULT_TOPICS = [
        'v3/info/charge/+' => 0,
    ];

    /**
     * 连接到MQTT服务器
     */
    public function connect(): void
    {
        try {
            // 配置MQTT服务器参数
            $config = config('mqtt', []);
            // 验证必要配置
            if (empty($config['host']) || empty($config['port'])) {
                throw new Exception("MQTT配置不完整，缺少主机或端口");
            }
            // 创建MQTT客户端实例
            $this->mqttClient = new Client("mqtt://{$config['host']}:{$config['port']}", [
                'username' => $config['username'] ?? '',
                'password' => $config['password'] ?? '',
                'client_id' => $config['client_id'] ?? uniqid('mqtt_', true),
                'keepalive' => $config['keepalive'] ?? 60,
                'clean_session' => $config['clean_session'] ?? true,
                'will' => $config['will'] ?? null,
            ]);
            // 设置回调函数
            $this->setupCallbacks();
            // 连接到MQTT服务器
            $this->mqttClient->connect();
            Log::info("MQTT客户端启动连接: {$config['host']}:{$config['port']}");
        } catch (Exception $e) {
            Log::error("MQTT客户端连接异常: {$e->getMessage()}");
            $this->handleReconnection();
        }
    }

    /**
     * 设置MQTT客户端回调函数
     */
    protected function setupCallbacks(): void
    {
        // 设置连接成功回调
        $this->mqttClient->onConnect = function ($client) {
            $this->connectionStatus = 'connected';
            // 重置重连尝试次数和定时器
            $this->reconnectAttempts = 0;
            $this->cancelReconnectTimer();
            Log::info("MQTT客户端连接成功");
            // 订阅默认主题
            $this->subscribeToTopics();
        };
        // 设置消息接收回调
        $this->mqttClient->onMessage = function ($topic, $content, $client) {
            $this->handleMessage($topic, $content);
        };
        // 设置错误回调
        $this->mqttClient->onError = function ($exception, $client) {
            $this->connectionStatus = 'error';
            Log::error("MQTT客户端发生错误: {$exception->getMessage()}");
            $this->handleReconnection();
        };
        // 设置连接关闭回调
        $this->mqttClient->onClose = function ($client) {
            $this->connectionStatus = 'disconnected';
            Log::info("MQTT客户端连接已关闭");
            $this->handleReconnection();
        };
    }

    /**
     * 订阅主题
     */
    protected function subscribeToTopics(): void
    {
        if (!$this->mqttClient || $this->connectionStatus !== 'connected') {
            Log::warning("MQTT客户端无法订阅主题: 客户端未连接");
            return;
        }
        if (empty(self::DEFAULT_TOPICS)) {
            return;
        }
        try {
            $this->mqttClient->subscribe(self::DEFAULT_TOPICS);
            $topics = implode(', ', array_keys(self::DEFAULT_TOPICS));
            Log::info("MQTT客户端已订阅主题: {$topics}");
        } catch (Exception $e) {
            Log::error("MQTT客户端订阅主题失败: {$e->getMessage()}");
        }
    }

    /**
     * 处理收到的消息
     */
    protected function handleMessage(string $topic, string $content): void
    {
        try {
            $message = [
                'topic' => $topic,
                'content' => $content,
                'time' => time(),
            ];
            Log::debug("MQTT客户端收到消息: " . json_encode($message, JSON_UNESCAPED_UNICODE));
            // 这里可以添加消息处理逻辑
            // 例如: 存储到数据库、触发事件等
        } catch (Exception $e) {
            Log::error("MQTT客户端处理消息时出错: {$e->getMessage()}");
        }
    }

    /**
     * 处理重连逻辑
     */
    protected function handleReconnection(): void
    {
        if ($this->reconnectAttempts >= self::MAX_RECONNECT_ATTEMPTS) {
            Log::error("MQTT客户端已达到最大重连次数({$this->reconnectAttempts}/" . self::MAX_RECONNECT_ATTEMPTS . ")，停止重连");
            return;
        }
        $this->reconnectAttempts++;
        $delay = self::RECONNECT_DELAY * pow(self::RECONNECT_BACKOFF_MULTIPLIER, $this->reconnectAttempts - 1);
        Log::info("MQTT客户端将在{$delay}秒后进行第{$this->reconnectAttempts}次重连尝试");
        // 取消之前的重连定时器
        $this->cancelReconnectTimer();
        $this->reconnectTimerId = Timer::add($delay, function () {
            $this->reconnectTimerId = null;
            $this->connect();
        }, null, false);
    }

    /**
     * 取消重连定时器
     */
    protected function cancelReconnectTimer(): void
    {
        if ($this->reconnectTimerId !== null) {
            Timer::del($this->reconnectTimerId);
            $this->reconnectTimerId = null;
        }
    }

    /**
     * 关闭连接
     */
    public function close(): void
    {
        // 取消重连定时器
        $this->cancelReconnectTimer();
        if ($this->mqttClient) {
            try {
                $this->mqttClient->close();
                $this->connectionStatus = 'disconnected';
                Log::info("MQTT客户端已正常关闭");
            } catch (Exception $e) {
                Log::error("MQTT客户端关闭过程中发生错误: {$e->getMessage()}");
            }
        }
    }

    /**
     * 获取连接状态
     */
    public function getConnectionStatus(): string
    {
        return $this->connectionStatus;
    }

    /**
     * 发布消息
     */
    public function publish(string $topic, string $message, int $qos = 0, bool $retain = false): bool
    {
        if (!$this->mqttClient || $this->connectionStatus !== 'connected') {
            Log::warning("MQTT客户端发布消息失败: 客户端未连接");
            return false;
        }
        try {
            $this->mqttClient->publish($topic, $message, $qos, $retain);
            Log::info("MQTT客户端成功发布消息到主题[{$topic}]: " . (strlen($message) > 100 ? substr($message, 0, 100) . '...' : $message));
            return true;
        } catch (Exception $e) {
            Log::error("MQTT客户端发布消息时出错: {$e->getMessage()}");
            return false;
        }
    }

    /**
     * 订阅主题
     */
    public function subscribe(array $topics): bool
    {
        if (!$this->mqttClient || $this->connectionStatus !== 'connected') {
            Log::warning("MQTT客户端订阅主题失败: 客户端未连接");
            return false;
        }
        try {
            $this->mqttClient->subscribe($topics);
            $topicList = implode(', ', array_keys($topics));
            Log::info("MQTT客户端已成功订阅主题: {$topicList}");
            return true;
        } catch (Exception $e) {
            Log::error("MQTT客户端订阅主题时出错: {$e->getMessage()}");
            return false;
        }
    }

    /**
     * 取消订阅主题
     */
    public function unsubscribe(array $topics): bool
    {
        if (!$this->mqttClient || $this->connectionStatus !== 'connected') {
            Log::warning("MQTT客户端取消订阅主题失败: 客户端未连接");
            return false;
        }
        try {
            $this->mqttClient->unsubscribe($topics);
            $topicList = implode(', ', $topics);
            Log::info("MQTT客户端已成功取消订阅主题: {$topicList}");
            return true;
        } catch (Exception $e) {
            Log::error("MQTT客户端取消订阅主题时出错: {$e->getMessage()}");
            return false;
        }
    }

    /**
     * 断开连接
     */
    public function disconnect(): void
    {
        $this->cancelReconnectTimer();
        if ($this->mqttClient) {
            try {
                $this->mqttClient->close();
                $this->connectionStatus = 'disconnected';
                Log::info("MQTT客户端已主动断开连接");
            } catch (Exception $e) {
                Log::error("MQTT客户端断开连接过程中发生错误: {$e->getMessage()}");
            }
        }
    }
}